package com.web.util;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.GregorianCalendar;

public class DateTime extends Date
{
  public static final int YEAR_TO_YEAR = 11;
  public static final int YEAR_TO_MONTH = 12;
  public static final int YEAR_TO_DAY = 13;
  public static final int YEAR_TO_HOUR = 14;
  public static final int YEAR_TO_MINUTE = 15;
  public static final int YEAR_TO_SECOND = 16;
  public static final int YEAR_TO_MILLISECOND = 17;
  public static final int MONTH_TO_MONTH = 22;
  public static final int MONTH_TO_DAY = 23;
  public static final int MONTH_TO_HOUR = 24;
  public static final int MONTH_TO_MINUTE = 25;
  public static final int MONTH_TO_SECOND = 26;
  public static final int MONTH_TO_MILLISECOND = 27;
  public static final int DAY_TO_DAY = 33;
  public static final int DAY_TO_HOUR = 34;
  public static final int DAY_TO_MINUTE = 35;
  public static final int DAY_TO_SECOND = 36;
  public static final int DAY_TO_MILLISECOND = 37;
  public static final int HOUR_TO_HOUR = 44;
  public static final int HOUR_TO_MINUTE = 45;
  public static final int HOUR_TO_SECOND = 46;
  public static final int HOUR_TO_MILLISECOND = 47;
  public static final int MINUTE_TO_MINUTE = 55;
  public static final int MINUTE_TO_SECOND = 56;
  public static final int MINUTE_TO_MILLISECOND = 57;
  public static final int SECOND_TO_SECOND = 66;
  public static final int SECOND_TO_MILLISECOND = 67;
  public static final int MILLISECOND_TO_MILLISECOND = 77;
  private static String delimiter;
  private int type = 0;
  private boolean empty = true;
  private static SimpleDateFormat yearToYearFormat;
  private static SimpleDateFormat yearToMonthFormat;
  private static SimpleDateFormat yearToDayFormat;
  private static SimpleDateFormat yearToHourFormat;
  private static SimpleDateFormat yearToMinuteFormat;
  private static SimpleDateFormat yearToSecondFormat;
  private static SimpleDateFormat yearToMillisecondFormat;
  private static SimpleDateFormat monthToMonthFormat;
  private static SimpleDateFormat monthToDayFormat;
  private static SimpleDateFormat monthToHourFormat;
  private static SimpleDateFormat monthToMinuteFormat;
  private static SimpleDateFormat monthToSecondFormat;
  private static SimpleDateFormat monthToMillisecondFormat;
  private static SimpleDateFormat dayToDayFormat;
  private static SimpleDateFormat dayToHourFormat;
  private static SimpleDateFormat dayToMinuteFormat;
  private static SimpleDateFormat dayToSecondFormat;
  private static SimpleDateFormat dayToMillisecondFormat;
  private static SimpleDateFormat hourToHourFormat;
  private static SimpleDateFormat hourToMinuteFormat;
  private static SimpleDateFormat hourToSecondFormat;
  private static SimpleDateFormat hourToMillisecondFormat;
  private static SimpleDateFormat minuteToMinuteFormat;
  private static SimpleDateFormat minuteToSecondFormat;
  private static SimpleDateFormat minuteToMillisecondFormat;
  private static SimpleDateFormat secondToSecondFormat;
  private static SimpleDateFormat secondToMillisecondFormat;
  private static SimpleDateFormat millisecondToMillisecondFormat;
  private static final long millsOneSecond = 1000L;
  private static final long millsOneMinute = 60000L;
  private static final long millsOneHour = 3600000L;
  private static final long millsOneDay = 86400000L;

  public DateTime()
  {
    this.empty = true;
  }

  public DateTime(Date date)
  {
    if (date == null) {
      this.empty = true;
    } else {
      setTime(date.getTime());
      this.empty = false;
      this.type = 13;
    }
  }

  public DateTime(String date)
  {
    this(date, 13);
  }

  public DateTime(String dateString, int type)
  {
    if ((dateString == null) || (dateString.trim().length() == 0)) {
      this.empty = true;
      return;
    }
    try {
      dateString = correct(dateString);
      SimpleDateFormat dateFormat = getDateFormat(type);
      Date date = dateFormat.parse(dateString);
      setTime(date.getTime());
      this.empty = false;
    } catch (ParseException e) {
      this.empty = true;
      throw new IllegalArgumentException("unable to parse " + dateString);
    }
    this.type = type;
    check(this, type);
  }

  public DateTime(Date date, int type)
  {
    setTime(date.getTime());
    this.empty = false;
    this.type = type;
  }

  public DateTime(DateTime dateTime, int type)
  {
    check(dateTime, type);
    setTime(dateTime.getTime());
    this.empty = false;
    this.type = type;
  }

  public int getYear()
  {
    check(this, 11);
    return Integer.parseInt(getDateFormat(11).format(this));
  }

  public int getMonth()
  {
    check(this, 22);
    return Integer.parseInt(getDateFormat(22).format(this));
  }

  public int getDay()
  {
    check(this, 33);
    return Integer.parseInt(getDateFormat(33).format(this));
  }

  public int getHour()
  {
    check(this, 44);
    return Integer.parseInt(getDateFormat(44).format(this));
  }

  public int getMinute()
  {
    check(this, 55);
    return Integer.parseInt(getDateFormat(55).format(this));
  }

  public int getSecond()
  {
    check(this, 66);
    return Integer.parseInt(getDateFormat(66).format(this));
  }

  public static void setDateDelimiter(String delimiter)
  {
    delimiter = delimiter;
    initDateFormat();
  }

  public static String getDateDelimiter()
  {
    return delimiter;
  }

  private static SimpleDateFormat getDateFormat(int type)
  {
    SimpleDateFormat dateFormat;
    switch (type)
    {
    case 11:
      dateFormat = yearToYearFormat;
      break;
    case 12:
      dateFormat = yearToMonthFormat;
      break;
    case 13:
      dateFormat = yearToDayFormat;
      break;
    case 14:
      dateFormat = yearToHourFormat;
      break;
    case 15:
      dateFormat = yearToMinuteFormat;
      break;
    case 16:
      dateFormat = yearToSecondFormat;
      break;
    case 17:
      dateFormat = yearToMillisecondFormat;
      break;
    case 22:
      dateFormat = monthToMonthFormat;
      break;
    case 23:
      dateFormat = monthToDayFormat;
      break;
    case 24:
      dateFormat = monthToHourFormat;
      break;
    case 25:
      dateFormat = monthToMinuteFormat;
      break;
    case 26:
      dateFormat = monthToSecondFormat;
      break;
    case 27:
      dateFormat = monthToMillisecondFormat;
      break;
    case 33:
      dateFormat = dayToDayFormat;
      break;
    case 34:
      dateFormat = dayToHourFormat;
      break;
    case 35:
      dateFormat = dayToMinuteFormat;
      break;
    case 36:
      dateFormat = dayToSecondFormat;
      break;
    case 37:
      dateFormat = dayToMillisecondFormat;
      break;
    case 44:
      dateFormat = hourToHourFormat;
      break;
    case 45:
      dateFormat = hourToMinuteFormat;
      break;
    case 46:
      dateFormat = hourToSecondFormat;
      break;
    case 47:
      dateFormat = hourToMillisecondFormat;
      break;
    case 55:
      dateFormat = minuteToMinuteFormat;
      break;
    case 56:
      dateFormat = minuteToSecondFormat;
      break;
    case 57:
      dateFormat = minuteToMillisecondFormat;
      break;
    case 66:
      dateFormat = secondToSecondFormat;
      break;
    case 67:
      dateFormat = secondToMillisecondFormat;
      break;
    case 77:
      dateFormat = millisecondToMillisecondFormat;
      break;
    case 18:
    case 19:
    case 20:
    case 21:
    case 28:
    case 29:
    case 30:
    case 31:
    case 32:
    case 38:
    case 39:
    case 40:
    case 41:
    case 42:
    case 43:
    case 48:
    case 49:
    case 50:
    case 51:
    case 52:
    case 53:
    case 54:
    case 58:
    case 59:
    case 60:
    case 61:
    case 62:
    case 63:
    case 64:
    case 65:
    case 68:
    case 69:
    case 70:
    case 71:
    case 72:
    case 73:
    case 74:
    case 75:
    case 76:
    default:
      throw new IllegalArgumentException(type + " is not support");
    }
    return dateFormat;
  }

  private static void initDateFormat()
  {
    yearToYearFormat = new SimpleDateFormat("yyyy");
    yearToMonthFormat = new SimpleDateFormat("yyyy" + delimiter + "MM");
    yearToDayFormat = new SimpleDateFormat("yyyy" + delimiter + "MM" + delimiter + "dd");
    yearToHourFormat = new SimpleDateFormat("yyyy" + delimiter + "MM" + delimiter + "dd HH");
    yearToMinuteFormat = new SimpleDateFormat("yyyy" + delimiter + "MM" + delimiter + "dd HH:mm");
    yearToSecondFormat = new SimpleDateFormat("yyyy" + delimiter + "MM" + delimiter + "dd HH:mm:ss");
    yearToMillisecondFormat = new SimpleDateFormat("yyyy" + delimiter + "MM" + delimiter + "dd HH:mm:ss.SSS");

    monthToMonthFormat = new SimpleDateFormat("MM");
    monthToDayFormat = new SimpleDateFormat("MM" + delimiter + "dd");
    monthToHourFormat = new SimpleDateFormat("MM" + delimiter + "dd HH");
    monthToMinuteFormat = new SimpleDateFormat("MM" + delimiter + "dd HH:mm");
    monthToSecondFormat = new SimpleDateFormat("MM" + delimiter + "dd HH:mm:ss");
    monthToMillisecondFormat = new SimpleDateFormat("MM" + delimiter + "dd HH:mm:ss.SSS");

    dayToDayFormat = new SimpleDateFormat("dd");
    dayToHourFormat = new SimpleDateFormat("dd HH");
    dayToMinuteFormat = new SimpleDateFormat("dd HH:mm");
    dayToSecondFormat = new SimpleDateFormat("dd HH:mm:ss");
    dayToMillisecondFormat = new SimpleDateFormat("dd HH:mm:ss.SSS");

    hourToHourFormat = new SimpleDateFormat("HH");
    hourToMinuteFormat = new SimpleDateFormat("HH:mm");
    hourToSecondFormat = new SimpleDateFormat("HH:mm:ss");
    hourToMillisecondFormat = new SimpleDateFormat("HH:mm:ss.SSS");

    minuteToMinuteFormat = new SimpleDateFormat("mm");
    minuteToSecondFormat = new SimpleDateFormat("mm:ss");
    minuteToMillisecondFormat = new SimpleDateFormat("mm:ss.SSS");

    secondToSecondFormat = new SimpleDateFormat("ss");
    secondToMillisecondFormat = new SimpleDateFormat("ss.SSS");

    millisecondToMillisecondFormat = new SimpleDateFormat("SSS");
  }

  public String toString()
  {
    if (this.empty == true) {
      return "";
    }
    SimpleDateFormat dateFormat = getDateFormat(this.type);
    return dateFormat.format(this);
  }

  public String toString(int type)
  {
    if (this.empty == true) {
      return "";
    }
    check(this, type);
    SimpleDateFormat dateFormat = getDateFormat(type);
    return dateFormat.format(this);
  }

  public static DateTime current()
  {
    SimpleDateFormat dateFormat = getDateFormat(17);
    return new DateTime(dateFormat.format(new Date()), 17);
  }

  public static int intervalYear(DateTime startDate, int startHour, DateTime endDate, int endHour)
  {
    if (startDate.getType() != 13) {
      throw new IllegalArgumentException("startDate is not a validate DateTime which type is YEAR_TO_DAY");
    }
    if (endDate.getType() != 13) {
      throw new IllegalArgumentException("endDate is not a validate DateTime which type is YEAR_TO_DAY");
    }
    startDate = new DateTime(startDate + " " + startHour, 14);
    endDate = new DateTime(endDate + " " + endHour, 14);
    int yearDiff = endDate.getYear() - startDate.getYear();
    if (endDate.getMonth() > startDate.getMonth())
      yearDiff++;
    else if (endDate.getMonth() < startDate.getMonth()) {
      yearDiff--;
    }
    else if (endDate.getDay() > startDate.getDay())
      yearDiff++;
    else if (endDate.getDay() < startDate.getDay()) {
      yearDiff--;
    }
    else if (endHour > startHour)
      yearDiff++;
    else if (endHour < startHour) {
      yearDiff--;
    }

    return yearDiff;
  }

  public static int intervalMonth(DateTime startDate, int startHour, DateTime endDate, int endHour)
  {
    if (startDate.getType() != 13) {
      throw new IllegalArgumentException("startDate is not a validate DateTime which type is YEAR_TO_DAY");
    }
    if (endDate.getType() != 13) {
      throw new IllegalArgumentException("endDate is not a validate DateTime which type is YEAR_TO_DAY");
    }
    startDate = new DateTime(startDate + " " + startHour, 14);
    endDate = new DateTime(endDate + " " + endHour, 14);
    int monthDiff = (endDate.getYear() - startDate.getYear()) * 12;
    if (endDate.getMonth() > startDate.getMonth()) {
      monthDiff += endDate.getMonth() - startDate.getMonth();
      if (endDate.getDay() > startDate.getDay())
        monthDiff++;
      else if (endDate.getDay() >= startDate.getDay())
      {
        if (endDate.getHour() > startDate.getHour())
          monthDiff++;
      }
    }
    else if (endDate.getMonth() < startDate.getMonth()) {
      monthDiff += endDate.getMonth() - startDate.getMonth();
      if (endDate.getDay() <= startDate.getDay()) {
        if (endDate.getDay() < startDate.getDay()) {
          monthDiff--;
        }
        else if (endDate.getHour() > startDate.getHour())
          monthDiff++;
        else if (endDate.getHour() < startDate.getHour()) {
          monthDiff--;
        }
      }
    }
    else if (endDate.getDay() > startDate.getDay()) {
      monthDiff++;
    } else if (endDate.getDay() < startDate.getDay()) {
      monthDiff--;
    }
    else if ((endDate.getHour() <= startDate.getHour()) && 
      (endDate.getHour() < startDate.getHour())) {
      monthDiff--;
    }

    return monthDiff;
  }

  public static int intervalDay(DateTime startDate, int startHour, DateTime endDate, int endHour)
  {
    if (startDate.getType() != 13) {
      throw new IllegalArgumentException("startDate is not a validate DateTime which type is YEAR_TO_DAY");
    }
    if (endDate.getType() != 13) {
      throw new IllegalArgumentException("endDate is not a validate DateTime which type is YEAR_TO_DAY");
    }
    long diffTime = endDate.getTime() + endHour * 3600000L - (startDate.getTime() + startHour * 3600000L);
    int diffDay = (int)(diffTime / 86400000L);
    long diffT = diffTime - diffDay * 86400000L;
    if (diffT > 0L)
      diffDay++;
    else if (diffT < 0L) {
      diffDay--;
    }
    return diffDay;
  }

  public DateTime addDay(int day)
  {
    DateTime dt = new DateTime(toString());
    dt.setTime(getTime() + day * 86400000L);
    return dt;
  }

  public DateTime addMonth(int iMonth)
  {
    DateTime dt = (DateTime)clone();
    GregorianCalendar gval = new GregorianCalendar();
    gval.setTime(dt);
    gval.add(2, iMonth);
    dt.setTime(gval.getTime().getTime());
    return dt;
  }

  public DateTime addYear(int iYear)
  {
    DateTime dt = (DateTime)clone();
    GregorianCalendar gval = new GregorianCalendar();
    gval.setTime(dt);
    gval.add(1, iYear);
    dt.setTime(gval.getTime().getTime());
    return dt;
  }

  public DateTime addHour(int hour)
  {
    DateTime dt = (DateTime)clone();
    dt.setTime(getTime() + hour * 3600000L);
    return dt;
  }

  public int getType()
  {
    return this.type;
  }

  private static String correct(String dateString)
  {
    String resultString = dateString;
    if (dateString.indexOf("/") > -1) {
      resultString = StringUtils.replace(dateString, "/", delimiter);
    }
    if (dateString.indexOf("-") > -1) {
      resultString = StringUtils.replace(dateString, "/", delimiter);
    }
    return resultString;
  }

  public boolean isEmpty()
  {
    return this.empty;
  }

  private void check(DateTime dateTime, int type)
  {
    if (dateTime.isEmpty()) {
      throw new IllegalStateException("DateTime is empty.");
    }
    int[] types = { 11, 12, 13, 14, 15, 16, 17, 22, 23, 24, 25, 26, 27, 33, 34, 35, 36, 37, 44, 45, 46, 47, 55, 56, 57, 66, 67, 77 };

    boolean isValidType = false;
    for (int i = 0; i < types.length; i++) {
      if (types[i] == type) {
        isValidType = true;
      }
    }

    if (!isValidType) {
      throw new IllegalStateException("this type is not support.");
    }

    if (dateTime.getType() != type)
    {
      if (dateTime.getType() / 10 > type / 10) {
        throw new IllegalStateException("this type is out of range of this datetime instance.");
      }

      if (dateTime.getType() % 10 < type % 10)
        throw new IllegalStateException("this type is out of range of this datetime instance.");
    }
  }

  static
  {
    setDateDelimiter("-");
  }

}
